/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Library General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor Boston, MA 02110-1301,  USA
 */

/**
 * @version bspd.c 0.0.1
 * @package Comoro
 * @author Dr.NP <np@bsgroup.org>
 * 
 * BSPD main file
 */
 
#include "bspd.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/resource.h>

#include <event.h>

extern struct conf setting;

/* Main event */
struct event_base *ev_base;

/* Process daemonize */
int daemonize()
{
	int fd;

	// Fork a new process
	switch(fork())
	{
		case -1 :
			return -1;
		case 0 :
			break;
		default :
			exit(1);
	}
	
	if (setsid() == -1)
	{
		return -1;
	}

	// Redirect standard IO to null device
	fd = open("/dev/null", O_RDWR, 0);
	if (fd)
	{
		(void) dup2(fd, STDIN_FILENO);
		(void) dup2(fd, STDOUT_FILENO);
		(void) dup2(fd, STDERR_FILENO);
		if (fd > STDERR_FILENO)
		{
			(void) close(fd);
		}
	}
	
	return 0;
}

/* Application terminate signal handler */
static void sig_handler(const int sig)
{
	fprintf(stderr, "\nSIGINT handled, server terminated\n");
	log_add("Server closed");

	// Clear all status
	conn_close();
	db_mysql_close();
	log_close();
	
    exit(EXIT_SUCCESS);

	return;
}

/* Set signal handler */
void set_sig()
{
	struct sigaction *sa = (struct sigaction *) malloc(sizeof(struct sigaction));
	
	// SIGINT
	signal(SIGINT, sig_handler);
	signal(SIGTERM, sig_handler);
	signal(SIGQUIT, sig_handler);
	signal(SIGKILL, sig_handler);

	// Ignore SIGPIPE & SIGCLD
	sa->sa_handler = SIG_IGN;
	sa->sa_flags = 0;
	signal(SIGPIPE, SIG_IGN);
	if (sigemptyset(&sa->sa_mask) == -1 || sigaction(SIGPIPE, sa, 0) == -1)
	{
		fprintf(stderr, "Ignore SIGPIPE failed\n");
		exit(EXIT_SIGNAL_ERROR);
	}

	return;
}

/* Set rlimit resources */
void set_rlimit()
{
	struct rlimit rlim, rlim_new;
	
	if (0 == getrlimit(RLIMIT_CORE, &rlim))
	{
		// Increase RLIMIT_CORE to infinity if possible
		rlim_new.rlim_cur = rlim_new.rlim_max = RLIM_INFINITY;

		if (0 != setrlimit(RLIMIT_CORE, &rlim_new))
		{
			// Set rlimit error
			rlim_new.rlim_cur = rlim_new.rlim_max = rlim.rlim_max;
			(void) setrlimit(RLIMIT_CORE, &rlim_new);

			log_add("Set rlimit max error, default max value will be used");
		}
	}

	if (0 != getrlimit(RLIMIT_CORE, &rlim) || rlim.rlim_cur == 0)
	{
		fprintf(stderr, "Failed to ensure core file creation\n");
		log_add("Failed to ensure core file creation");

		exit(EXIT_GENERAL);
	}

	// Read current RLIMIT_NOFILE
	if (0 != getrlimit(RLIMIT_NOFILE, &rlim))
	{
		fprintf(stderr, "Failed to getrlimit number of files\n");
		log_add("Failed to getrlimit number of files");

		exit(EXIT_GENERAL);
	}

	else
	{
		// Enlarge RLIMIT_NOFILE to allow as many connections as we need
		int maxfiles = MAX_CLIENT;

		if (rlim.rlim_cur < maxfiles + 3)
		{
			rlim.rlim_cur = maxfiles + 3;
		}

		if (rlim.rlim_max < rlim.rlim_cur)
		{
			if (0 == getuid() || 0 == geteuid())
			{
				fprintf(stderr, "Increase NOFILE hard_limit from %d to %d\n", (int) rlim.rlim_max, (int) rlim.rlim_cur);
				rlim.rlim_max = rlim.rlim_cur;
			}

			else
			{
				fprintf(stderr, "You cannot modify NOFILE hard_limit out of superuser privilege\n");
				rlim.rlim_cur = rlim.rlim_max;
			}
		}

		if (0 != setrlimit(RLIMIT_NOFILE, &rlim))
		{
			// Set error
			fprintf(stderr, "Failed to set rlimit for open files, try root privilege.\n");
			log_add("Failed to set rlimit for open files");

			exit(EXIT_GENERAL);
		}
	}
	
	return;
}

/* Set current working directory */
void set_pwd()
{
	// Change current working directory to the runner root, so lua scripts can use relative path
	if (0 == chdir(setting.runner_base_dir))
	{
		log_add("Current working directory set to %s", setting.runner_base_dir);
	}
	
	else
	{
		log_add("Directory change error, working at %s", getcwd(NULL, 0));
	}
	
	return;
}

/* Drive the Server! */
int main(int argc, char **argv)
{
	int c;
	char *config_file = (char *) NULL;
	char *log_file = (char *) NULL;
	int bridge_mode = -1;
	
	// Read argv
	while((c = getopt(argc, argv, "f:m:l:")) != -1)
	{
		switch(c)
		{
			case 'f' : 
				config_file = strdup(optarg);
				break;
			
			case 'm' : 
				bridge_mode = atoi(optarg);
				break;

			case 'l' :
				log_file = strdup(optarg);
			
			default :
				// Who can save your keyboard?
				break;
		}
	}

	// Init configure parser
	if (!config_file)
	{
		config_file = DEFAULT_CONFIG_FILE;
	}
	
	conf_init(config_file);
	
	// Ready for daemonize ?
	if (setting.srv_daemonize)
	{
		daemonize();
	}

	else
	{
		fprintf(stderr, "\n* Comoro server %s *\n====== Powered by BS.Group    ======\n====== Dr.NP <np@bsgroup.org> ======\n\n", BSP_VERSION);
	}

	// Set working directory
	set_pwd();
	
	// Init log
	log_init(log_file);

	// Set signations
	set_sig();

	// Set rlimit
	set_rlimit();

	// Event initialize
	ev_base = event_init();

	// Try start
	log_add("Server started");

	if (ev_base)
	{
		fprintf(stderr, "Main event initiailzed\n");
	}

	else
	{
		fprintf(stderr, "Libevent error\n");
		log_add("Libevent error");

		exit(EXIT_GENERAL);
	}
	
	// Queue initialize
	queue_init();

	// Determine server mode and start main base loop
	if (BRIDGE_MODE_LOGIN == bridge_mode || (0 == strncasecmp("login", setting.srv_mode, 5) && bridge_mode == -1))
	{
		// Login server mode
		thread_init();
		bridge_init(BRIDGE_MODE_LOGIN);
		conn_init();
		login_init();
	}

	else if (BRIDGE_MODE_UNITED == bridge_mode || (0 == strncasecmp("united", setting.srv_mode, 6) && bridge_mode == -1))
	{
		// Joined together
		thread_init();
		bridge_init(BRIDGE_MODE_UNITED);
		conn_init();
		center_init();
		status_init();
		base_init();
	}
	
	else if (BRIDGE_MODE_BASE == bridge_mode || (0 == strncasecmp("base", setting.srv_mode, 4) && bridge_mode == -1))
	{
		// Base server mode
		thread_init();
		bridge_init(BRIDGE_MODE_BASE);
		conn_init();
		base_init();
	}
	
	else if (BRIDGE_MODE_CENTER == bridge_mode || (0 == strncasecmp("center", setting.srv_mode, 6) && bridge_mode == -1))
	{
		// Center server mode
		thread_init();
		bridge_init(BRIDGE_MODE_CENTER);
		conn_init();
		center_init();
		status_init();
	}
	
	else if (BRIDGE_MODE_COIN == bridge_mode || (0 == strncasecmp("coin", setting.srv_mode, 4) && bridge_mode == -1))
	{
		// Coin server mode
		bridge_init(BRIDGE_MODE_COIN);
		coin_init();
	}

	else
	{
		// What are you doing???
		exit(EXIT_GENERAL);
	}

	// Dispatch main event base
	event_base_loop(ev_base, 0);
	
	// See you next year!!!
	conn_close();
	db_mysql_close();
	log_close();
	
	return EXIT_SUCCESS;
}
